// Learn TypeScript:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/life-cycle-callbacks.html

import { Component, Vec2, _decorator, v2 } from "cc";
export interface IGridMap {
    crd2lp(crd: Vec2): Vec2;
    findPath(crd1: Vec2, crd2: Vec2): PathInfo;
}

const { ccclass, property } = _decorator;
class MoveInfoBase {
    public node: IMoveNode = null;
    public map: IGridMap;
    constructor(node: IMoveNode, map: IGridMap) {
        this.node = node;
        this.map = map;
    }
    public tick(dt: number): boolean {
        return true;
    }
    public end() {

    }
    public stop() {

    }
    public start() {

    }
}
class MoveInfoDirect extends MoveInfoBase {
    public startPos: Vec2 = null;
    public endPos: Vec2 = null;
    public time: number = 0;
    public endCall: (pos: Vec2) => void;
    public easing: (t: number) => number;
    private timer = 0;
    constructor(node: IMoveNode, map: IGridMap, time: number, easing: (t: number) => number, startPos: Vec2, endPos: Vec2, endCall: (pos: Vec2) => void) {
        super(node, map);
        this.startPos = startPos;
        this.endPos = endPos;
        this.time = time;
        this.easing = easing;
        this.endCall = endCall;
    }
    public start(): void {
        this.timer = 0;
    }
    public tick(dt: number): boolean {
        this.timer += dt;

        if (this.timer >= this.time) {
            this.node.setLayerPos(this.endPos);

            return true;
        }
        else {
            let pos = Vec2.lerp(v2(), this.startPos, this.endPos, this.easing(this.timer / this.time));
            this.node.setLayerPos(pos);

        }
        return false;
    }
    public end(): void {
        this.node.setLayerPos(this.endPos);
        this.endCall && this.endCall(this.endPos);
    }
}
class MoveInfoByCrd extends MoveInfoBase {
    public path: PathInfo = null;
    public tickCall: (dir: Vec2) => void;
    public endCall: (endCrd: Vec2) => void;
    public cancelCall: (pos: Vec2) => void;
    public getCrdCall: (crd: Vec2) => void;
    public speed: number = 0;

    public _pathIdx: number = 0;
    public _startPos: Vec2;
    public _nextPos: Vec2;
    public _targetPos: Vec2;
    public _curPos: Vec2;
    public tick(dt: number): boolean {
        let crd2Pos = (crd: Vec2) => this.map.crd2lp(crd);
        if (this._pathIdx == -1) {
            this._targetPos = crd2Pos(this.path.startCrd);
            if (this.path.pathCrds.length > 1) {
                let curPos = this._curPos;
                let nextPos = crd2Pos(this.path.pathCrds[1]);
                if (Vec2.dot(this._targetPos.subtract(curPos), nextPos.subtract(curPos)) <= 0) {
                    this._targetPos = nextPos;
                    this._pathIdx = 0;
                }
            }
        }
        else if (this._pathIdx >= this.path.pathCrds.length - 1) {
            return true;
        }
        else {
            this._targetPos = crd2Pos(this.path.pathCrds[this._pathIdx + 1]);
        }
        let dir = this._targetPos.subtract(this._startPos).normalize();
        this.tickCall && this.tickCall(dir);
        let nextPos = this.node.getLayerPos().add(dir.multiplyScalar(dt * this.speed));
        if (Vec2.dot(dir, this._targetPos.subtract(nextPos)) <= 0) {
            this._pathIdx++;
            this._startPos = this._targetPos;
            this.node.setLayerPos(this._targetPos);

            this.getCrdCall && this.getCrdCall(this.path.pathCrds[this._pathIdx]);
        }
        else {
            this.node.setLayerPos(nextPos);
        }
        return false;
    }
    public start() {
        this._pathIdx = -1;
        this._startPos = this.node.getLayerPos();
        this._curPos = this.node.getLayerPos();
        this._nextPos = v2();
        this._targetPos = v2();
    }

    public end() {
        this.node.setLayerPos(this.map.crd2lp(this.path.endCrd));
        this.endCall && this.endCall(this.path.endCrd);
    }
    public stop() {
        this.cancelCall && this.cancelCall(this.node.getLayerPos());

    }
    constructor(node: IMoveNode, map: IGridMap, speed: number, startCrd: Vec2, endCrd: Vec2, tickCall: (dir: Vec2) => void, endCall: (endCrd: Vec2) => void, cancelCall: (pos: Vec2) => void, getCrdCall: (crd: Vec2) => void) {
        super(node, map);
        this.path = this.map.findPath(startCrd, endCrd);
        this.tickCall = tickCall;
        this.endCall = endCall;
        this.cancelCall = cancelCall;
        this.getCrdCall = getCrdCall;
        this.speed = speed;
        this._pathIdx = -1;
    }
}
export interface IMoveNode {
    setLayerPos(pos: Vec2);
    getLayerPos(): Vec2;
}
@ccclass
export default class MoveManager extends Component {
    private static map: Map<IMoveNode, MoveInfoBase> = new Map();
    // public static stepTo(node: IMoveNode, endCrd: Vec2, tickCall: (crd: Vec2) => void, endCall: (endCrd: Vec2) => void, cancelCall: (pos: Vec2) => void, getCrdCall: (crd: Vec2) => void) {
    //     if (MoveManager.map.has(node)) {
    //         MoveManager.map.delete(node);
    //     }
    //     let info = new MoveInfoByStep(node, node.getLayerPos(), endCrd, tickCall, endCall, cancelCall, getCrdCall);
    //     if (info.path.isEmpty) {
    //         UI.getUI<U_TaskTips>(U_TaskTips.name).setTxt("无法到达");
    //         info.cancelCall && info.cancelCall(node.getLayerPos());
    //         return;
    //     }
    //     this.map.set(node, info);
    //     this.startPath(info);
    // }
    public static moveTo(node: IMoveNode, map: IGridMap, speed: number, startCrd: Vec2, endCrd: Vec2, tickCall: (dir: Vec2) => void, endCall: (endPos: Vec2) => void, cancelCall: (pos: Vec2) => void, getCrdCall: (crd: Vec2) => void) {

        if (MoveManager.map.has(node)) {
            MoveManager.map.delete(node);
        }
        let info = new MoveInfoByCrd(node, map, speed, startCrd, endCrd, tickCall, endCall, cancelCall, getCrdCall);
        if (info.path.isEmpty) {
            info.cancelCall && info.cancelCall(node.getLayerPos());
            return;
        }
        this.map.set(node, info);
        info.start();
    }
    public static directTo(node: IMoveNode, map: IGridMap, time: number, easing: (t: number) => number, startPos: Vec2, endPos: Vec2, endCall: (endPos: Vec2) => void) {
        if (MoveManager.map.has(node)) {
            MoveManager.map.delete(node);
        }
        let info = new MoveInfoDirect(node, map, time, easing, startPos, endPos, endCall);
        this.map.set(node, info);
        info.start();
    }
    public static stopMove(node: IMoveNode) {
        let info = MoveManager.map.get(node);
        if (info) {
            info.stop();
            MoveManager.map.delete(node);
        }
    }

    public update(dt: number) {
        let map = MoveManager.map;
        let endInfo: MoveInfoBase[] = [];
        map.forEach((v, k) => {
            if (v.tick(dt))
                endInfo.push(v);
        });
        endInfo.forEach(v => {
            MoveManager.map.delete(v.node);
            v.end();
        });
    }
}
export class PathInfo {
    public get isEmpty() { return this.pathCrds.length == 0; }
    public pathCrds: Vec2[] = [];
    public get startCrd() { return this.isEmpty ? null : this.pathCrds[0]; }
    public get endCrd() { return this.isEmpty ? null : this.pathCrds[this.pathCrds.length - 1]; }
    constructor(pathCrd: Vec2[] = []) {
        this.pathCrds = pathCrd;
    }
    public toString() {
        return this.pathCrds.map((v) => `(${v.x},${v.y})`).join("->");
    }
}
